package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.list.dao.GoodsDao;
import com.atguigu.gmall.list.service.ListService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuAttrValue;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import net.bytebuddy.asm.Advice;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 李旭
 * @date 2022/1/12 10:25
 * @Description:
 */
@Service
public class ListServiceImpl implements ListService {

    //ES官方客户端  DSl
    @Autowired
    private RestHighLevelClient restHighLevelClient;//高级 性能高  查询  DSL语句离的更近一些
    //Spring封装的
    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;//封装高级 性能稍微低


    @Autowired
    private ProductFeignClient productFeignClient;
    @Autowired
    private GoodsDao goodsDao;//CRUD  //Lucene  :修改 底层 先删除 再添加
    @Autowired
    private RedisTemplate redisTemplate;

    //上架  添加索引
    @Override
    public void onSale(Long skuId) {
        Goods goods = new Goods();
        //1:查询SKuInfo信息
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        goods.setId(skuInfo.getId());
        goods.setTitle(skuInfo.getSkuName());
        goods.setDefaultImg(skuInfo.getSkuDefaultImg());
        goods.setPrice(skuInfo.getPrice().doubleValue());
        //2:创建时间
        Calendar calendar = Calendar.getInstance();
        //calendar.add(Calendar.DATE,1);
        goods.setCreateTime(calendar.getTime());
        //3:查询品牌
        BaseTrademark baseTrademark = productFeignClient.getBaseTrademarkById(skuInfo.getTmId());
        goods.setTmId(baseTrademark.getId());
        goods.setTmName(baseTrademark.getTmName());
        goods.setTmLogoUrl(baseTrademark.getLogoUrl());
        //4:分类视图
        BaseCategoryView baseCategoryView = productFeignClient.
                getBaseCategoryView(skuInfo.getCategory3Id());
        goods.setCategory1Id(baseCategoryView.getCategory1Id());
        goods.setCategory1Name(baseCategoryView.getCategory1Name());
        goods.setCategory2Id(baseCategoryView.getCategory2Id());
        goods.setCategory2Name(baseCategoryView.getCategory2Name());
        goods.setCategory3Id(baseCategoryView.getCategory3Id());
        goods.setCategory3Name(baseCategoryView.getCategory3Name());
        //5:根据skuId查询平台属性及属性值
        List<SkuAttrValue> skuAttrValueList =
                productFeignClient.getSkuAttrValueList(skuInfo.getId());
        List<SearchAttr> attrs = skuAttrValueList.stream().map(skuAttrValue -> {
            SearchAttr searchAttr = new SearchAttr();
            searchAttr.setAttrId(skuAttrValue.getBaseAttrInfo().getId());
            searchAttr.setAttrName(skuAttrValue.getBaseAttrInfo().getAttrName());
            searchAttr.setAttrValue(skuAttrValue.getBaseAttrValue().getValueName());
            return searchAttr;
        }).collect(Collectors.toList());
        goods.setAttrs(attrs);
        //添加
        goodsDao.save(goods);
    }


    //下架  删除索引
    @Override
    public void cancelSale(Long skuId) {
        goodsDao.deleteById(skuId);
    }

    //商品热度排序  （ 加分的接口）
    @Override
    public void hotScore(Long skuId, Long score) {

        //1:先保存在缓存中  数据达到一定阈值
        /**
         * 五大数据类型：
         *   1、String类型  字符串类型 值类型
         *   2、List类型   列表类型
         *   3、Set类型    集合类型  （保存用户权限的）
         *   4、ZSet类型   有序集合类型
         *   5、Hash类型   散列类型
         *
         *   ZSet类型：
         *      小明   语文 100分  数学 88分  英语 78分
         *      小黄
         *
         *
         */
        //总分
        Double hotScore = redisTemplate.opsForZSet().incrementScore("hotScore", skuId, score);
        System.out.println(skuId + ":商品被访问要求+" + score + "分");
        if (hotScore % 1000 == 0) {
            //2:统一保存到ES中
            Optional<Goods> byId = goodsDao.findById(skuId);
            Goods goods = byId.get();
            goods.setHotScore(Math.round(hotScore));
            goodsDao.save(goods);//慢

            System.out.println(skuId + ":商品被访问要求+" + score + "分:保存到索引库");
        }


    }

    //开始搜索
    @Override
    public SearchResponseVo list(SearchParam searchParam) {
        //1:构建搜索请求对象
        SearchRequest searchRequest = buildSearchRequest(searchParam);

        SearchResponse searchResponse = null;
        try {
            //2：执行搜索
            searchResponse =
                    restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //3:解析搜索结果
        SearchResponseVo vo = parseSearchResponse(searchResponse);
        vo.setPageNo(searchParam.getPageNo());
        vo.setPageSize(searchParam.getPageSize());
        //计算总页数
        Long totalPages = (vo.getTotal() + vo.getPageSize() -1)/vo.getPageSize();
        vo.setTotalPages(totalPages);
        return vo;
    }


    //解析搜索结果
    private SearchResponseVo parseSearchResponse(SearchResponse searchResponse) {
        SearchResponseVo vo = new SearchResponseVo();

        SearchHits hits = searchResponse.getHits();
        //总条数
        long totalHits = hits.totalHits;
        vo.setTotal(totalHits);
        System.out.println("total:" + totalHits);
        //商品集合
        SearchHit[] hits1 = hits.getHits();
        List<Goods> goodsList = Arrays.stream(hits1).map(searchHit -> {
            //JSON
            String sourceAsString = searchHit.getSourceAsString();
            //JSON转成对象
            Goods goods = JSONObject.parseObject(sourceAsString, Goods.class);
            //高亮
            Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
            if(null != highlightFields && highlightFields.size() > 0){
                HighlightField title = highlightFields.get("title");
                String h = title.fragments()[0].toString();
                //有高亮 优先使用高亮的域
                goods.setTitle(h);
            }
            return goods;
        }).collect(Collectors.toList());
        vo.setGoodsList(goodsList);
        //品牌集合
        ParsedLongTerms tmIdAgg = searchResponse.getAggregations().get("tmIdAgg");
        List<SearchResponseTmVo> tmVoList = tmIdAgg.getBuckets().stream().map(bucket -> {
            SearchResponseTmVo tmVo = new SearchResponseTmVo();
            //品牌Id
            tmVo.setTmId(bucket.getKeyAsNumber().longValue());
            //品牌名称
            ParsedStringTerms tmNameAgg = ((Terms.Bucket) bucket).getAggregations().get("tmNameAgg");
            tmVo.setTmName(tmNameAgg.getBuckets().get(0).getKeyAsString());
            //品牌的LOGO
            ParsedStringTerms tmLogoUrlAgg = ((Terms.Bucket) bucket).getAggregations().get("tmLogoUrlAgg");
            tmVo.setTmLogoUrl(tmLogoUrlAgg.getBuckets().get(0).getKeyAsString());
            return tmVo;
        }).collect(Collectors.toList());
        vo.setTrademarkList(tmVoList);

        //
        ParsedNested attrsAgg = searchResponse.getAggregations().get("attrsAgg");
        ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attrIdAgg");
        List<SearchResponseAttrVo> attrsList = attrIdAgg.getBuckets().stream().map(bucket -> {
            SearchResponseAttrVo attrVo = new SearchResponseAttrVo();
            //平台属性ID
            attrVo.setAttrId(bucket.getKeyAsNumber().longValue());
            //平台属性名称
            ParsedStringTerms attrNameAgg = ((Terms.Bucket) bucket).
                    getAggregations().get("attrNameAgg");
            attrVo.setAttrName(attrNameAgg.getBuckets().get(0).getKeyAsString());
            //平台属性值集合
            ParsedStringTerms attrValueAgg = ((Terms.Bucket) bucket).
                    getAggregations().get("attrValueAgg");
            List<String> attrValueList = attrValueAgg.getBuckets().stream()
                    .map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
            attrVo.setAttrValueList(attrValueList);
            return attrVo;
        }).collect(Collectors.toList());
        vo.setAttrsList(attrsList);
        return vo;
    }
    //构建搜索请求对象
    private SearchRequest buildSearchRequest(SearchParam searchParam) {

        //GET _search
        SearchRequest searchRequest = new SearchRequest();
        /**
         * {
         *}
         */
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        //组合条件对象
        /**
         *  must :   AND
         *  must_not: NOT
         *  should : OR
         *  //  select * from 表 where title = '' NOT
         */
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //1:判断是否有关键词
        String keyword = searchParam.getKeyword();
        if(!StringUtils.isEmpty(keyword)){
            //match:匹配  1、先分词 2、再查询  3、取并集   手机  华为
            //  select * from 表 where title like '%手机%'  AND title like 中国人 AND  title like 国人
            boolQueryBuilder.must(
                    QueryBuilders.matchQuery("title",keyword).operator(Operator.AND));
        }else{
            /**
             *   "query": {
             *     "match_all": {}
             *   }
             */
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());//查询所有
        }
        //2:品牌 //trademark=2:华为   term:精准查询  select * from 表 where title == '中国'
        String trademark = searchParam.getTrademark();
        if(!StringUtils.isEmpty(trademark)){
            String[] t = trademark.split(":");
            boolQueryBuilder.filter(QueryBuilders.termQuery("tmId",t[0]));
        }
        //3:一二三级分类
        Long category1Id = searchParam.getCategory1Id();
        if(null != category1Id){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id",category1Id));
        }
        Long category2Id = searchParam.getCategory2Id();
        if(null != category2Id){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id",category2Id));
        }
        Long category3Id = searchParam.getCategory3Id();
        if(null != category3Id){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id",category3Id));
        }
        //4:平台属性过滤
        String[] props = searchParam.getProps();
        if(null!= props && props.length > 0){
            for (String prop : props) {
                //prop 3:4G:运行内存  平台属性ID:平台属性值:平台属性名称
                String[] p = prop.split(":");
                BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();
                subBoolQuery.must(QueryBuilders.termQuery("attrs.attrId",p[0]));
                subBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue",p[1]));
                //像集合 代表 []
                boolQueryBuilder.filter(QueryBuilders.nestedQuery("attrs",subBoolQuery,ScoreMode.None));
            }
        }
        //将上面的条件全部放到组合对象中的时候
        searchSourceBuilder.query(boolQueryBuilder);
        //排序   // order=1:desc   1:综合  2：销量 3:评论数 4：新品 5:价格
        String order = searchParam.getOrder();
        if(!StringUtils.isEmpty(order)){
            String[] o = order.split(":");
            String field = "";
            switch (o[0]){
                case "1" : field = "hotScore";break;
                case "2" : field = "price";break;
                case "3" : field = "createTime";break;
            }
            searchSourceBuilder.sort(field,
                    o[1].equalsIgnoreCase("desc")?SortOrder.DESC:SortOrder.ASC);
        }else{
            //默认值  综合 == 热度分  高到低
            searchSourceBuilder.sort("hotScore", SortOrder.DESC);
        }
        //分页
        Integer pageSize = searchParam.getPageSize();
        Integer pageNo = searchParam.getPageNo();
        searchSourceBuilder.from((pageNo-1)*pageSize);//开始行
        searchSourceBuilder.size(pageSize);//每页数

        //高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title").preTags("<font style='color:red'>").postTags("</font>");
        searchSourceBuilder.highlighter(highlightBuilder);
            //高亮的域
            //前缀
            //后缀
        //桶聚合  品牌    品牌ID:品牌的名称:品牌的Logo图片
        searchSourceBuilder.aggregation(
                AggregationBuilders.terms("tmIdAgg").field("tmId").size(100)
                .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName").size(100))
                .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl").size(100)));

        //桶聚合 平台属性
        searchSourceBuilder.aggregation(
                AggregationBuilders.nested("attrsAgg","attrs")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                .subAggregation(AggregationBuilders.terms("attrValueAgg")
                        .field("attrs.attrValue"))));



        System.out.println("DSL:" + searchSourceBuilder.toString());

        //将请求对象与大括号连起来
        searchRequest.source(searchSourceBuilder);
        //构建完成马上返回
        searchRequest.indices("goods");
        //searchRequest.types("info","fssf");// ES 7以前是有的  ES7就废弃了
        return searchRequest;
    }
}
